import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, In, MoreThan, MoreThanOrEqual } from 'typeorm';
import Decimal from 'decimal.js';
import { InvestFunding } from '../entity/investFunding.entity';
import { InvestInterest } from '../entity/investInterest.entity';
import { InvestFundingPlusType, InvestFundingMinusType } from '../entity/investFunding.entity';
import { Account, AccountRole } from '@/account/entity/account.entity';
import { InvestOrder } from '../entity/investOrder.entity';
import { AppException } from '@libs/exception';
import { Stage } from '../entity/stage.entity';
import { InterestFeeService } from './interest.service';

interface ActiveInvestment {
  amount: Decimal;
  rate: Decimal;
  manageFeeRate: Decimal;
  investDate: Date;
  startDate: Date;
  endDate: Date;
}

@Injectable()
export class InvestInterestService {
  constructor(
    @InjectRepository(InvestFunding)
    private readonly investFundingRepo: Repository<InvestFunding>,
    @InjectRepository(InvestOrder)
    private readonly investOrderRepo: Repository<InvestOrder>,
    @InjectRepository(Account)
    private readonly accountRepo: Repository<Account>,
    @InjectRepository(InvestInterest)
    private readonly investInterestRepo: Repository<InvestInterest>,
    @InjectRepository(Stage)
    private readonly stageRepo: Repository<Stage>,
    private interestService: InterestFeeService,
  ) {}

  async updateInterest(stageId: number, investorId: number, isTransfer = false) {
    // Get all funding records for this investor and stage
    const fundings = await this.investFundingRepo.find({
      where: {
        stageId,
        investorId,
        type: In([InvestFundingPlusType.INVESTMENT, InvestFundingMinusType.WITHDRAWAL]),
      },
      order: {
        startDate: 'ASC',
      },
    });
    const stage = await this.stageRepo.findOneBy({ id: stageId });
    const investor = await this.accountRepo.findOneBy({ id: investorId });
    // Process funding records to calculate interest
    const activeInvestments = this.processFundingPeriods(stage, fundings, investor);

    // Calculate and save daily interest
    await this.calculateAndSaveInterest(activeInvestments, stage, investor, isTransfer);
  }

  private processFundingPeriods(stage: Stage, fundings: InvestFunding[], investor: Account): ActiveInvestment[] {
    // Track active investments with their remaining amounts
    let activeInvestments: Array<ActiveInvestment> = [];
    fundings = fundings.filter((f) => f.type == InvestFundingMinusType.WITHDRAWAL || f.type == InvestFundingPlusType.INVESTMENT);
    for (const funding of fundings) {
      if (funding.type === InvestFundingPlusType.INVESTMENT) {
        // Add new investment
        activeInvestments.push({
          amount: funding.value,
          rate: funding.incomeRate || new Decimal(0),
          // manageFeeRate: investor.role == AccountRole.COMPNAY ? funding.manageFeeRate || new Decimal(0) : new Decimal(0),
          manageFeeRate: funding.manageFeeRate,
          investDate: funding.startDate,
          startDate: stage.loanStartDate,
          endDate: stage.loanRepaymentDate,
        });
      } else if (funding.type === InvestFundingMinusType.WITHDRAWAL) {
        // Process withdrawal by deducting from earliest investments first
        let remainingWithdrawal = funding.value;
        let index = 0;
        let canWithdraw = false;
        for (const investment of activeInvestments) {
          if (investment.endDate.getTime() < funding.startDate.getTime()) {
            continue;
          }
          canWithdraw = true;
          if (remainingWithdrawal.lessThanOrEqualTo(0)) break;

          if (investment.amount.lessThanOrEqualTo(remainingWithdrawal)) {
            investment.endDate = funding.startDate;
            remainingWithdrawal = remainingWithdrawal.minus(investment.amount);
          } else {
            const left = investment.amount.minus(remainingWithdrawal);
            activeInvestments.splice(index + 1, 0, {
              amount: left,
              rate: investment.rate,
              manageFeeRate: investment.manageFeeRate,
              investDate: funding.startDate,
              startDate: funding.startDate,
              endDate: investment.endDate,
            });
            investment.endDate = funding.startDate;
            remainingWithdrawal = new Decimal(0);
          }
          index++;
        }
        if (canWithdraw) {
          new Error('已投资金额小于提现金额')
        }
      }
    }

    return activeInvestments.sort((a, b) => a.investDate.getTime() - b.investDate.getTime());
  }

  private async calculateAndSaveInterest(activeInvestments: ActiveInvestment[], stage: Stage, investor: Account, isTransfer=false) {
    // Delete existing interest records for this investor and stage
    await this.investInterestRepo.delete({
      stageId: stage.id,
      investorId: investor.id,
    });

    const interests: Record<string, InvestInterest> = {};
    const dayMisSecond = 1000 * 60 * 60 * 24;
    for (const invest of activeInvestments) {
      let days = Math.ceil((invest.endDate.getTime() - invest.investDate.getTime()) / (dayMisSecond));
      // 非官方投资人，投资天数减一
      if ((isTransfer || investor.isOffice) && invest.endDate.toDateString() === stage.loanRepaymentDate.toDateString()) {
        days = days + 1
      }

      // Calculate daily interest for each day in the period
      for (let i = 0; i < days; i++) {
        const currentDate = new Date(invest.investDate);
        currentDate.setDate(currentDate.getDate() + i);

        // Calculate total daily interest from all active investments
        const dailyInterest = this.interestService.calculateDailyInterest(stage.interestMethod, invest.amount, invest.rate, stage.loanStartDate, currentDate);
        let investMoney = invest.amount;
        let money = dailyInterest;
        let net = dailyInterest;
        let tax = new Decimal(0);
        let fee = new Decimal(0);
        
        if (invest.manageFeeRate.gt(0)) {
          const netRate = new Decimal(1).sub(invest.manageFeeRate);
          fee = net.mul(invest.manageFeeRate).div(netRate);
          money = net.add(fee);
        }
        if (!investor.isTaxResidency) {
          tax = net.mul(0.1);
          net = net.mul(0.9);
        }
        const current = currentDate.toISOString();
        if (interests[current]) {
          interests[current].investMoney = interests[current].investMoney.add(investMoney);
          interests[current].money = interests[current].money.add(dailyInterest);
          interests[current].net = interests[current].net.add(net);
          interests[current].tax = interests[current].tax.add(tax);
          interests[current].fee = interests[current].fee.add(fee);
        } else {
          interests[current] = this.investInterestRepo.create({
            stageId: stage.id,
            investorId: investor.id,
            date: currentDate,
            investMoney,
            money,
            net,
            fee,
            tax,
          });
        }
      }
    }
    await this.investInterestRepo.save(Object.values(interests));
  }

  async calculateMonthlyInterest(stageId: number, investorId: number, newRepaymentDate?: Date): Promise<void> {
    // Create query builder for deletion
    let deleteQuery = this.investFundingRepo
      .createQueryBuilder()
      .delete()
      .where('stageId = :stageId', { stageId })
      .andWhere('investorId = :investorId', { investorId })
      .andWhere('type IN (:...types)', {
        types: [
          InvestFundingPlusType.TAX_WITHOLDING,
          InvestFundingPlusType.INTEREST_DISTRIBUTION,
          InvestFundingMinusType.TAX_PAYMENT,
          InvestFundingMinusType.INTEREST_EARN,
          InvestFundingPlusType.MANAGEMENT_CHARGE,
          InvestFundingMinusType.MANAGEMENT_PAYMENT,
        ]
      });

    if (newRepaymentDate) {
      deleteQuery = deleteQuery.andWhere('endDate >= :repaymentDate', { repaymentDate: new Date(newRepaymentDate) });
    }

    // Execute the composed query
    await deleteQuery.execute();
    const stage = await this.stageRepo.findOneBy({id: stageId});

    // Get investor account to check tax residency
    const investor = await this.investFundingRepo.manager.findOne(Account, {
      where: { id: investorId },
    });

    if (!investor) {
      throw new AppException('Investor account not found ', { httpCode: 400 });
    }

    // Get investment period
    const order = await this.investFundingRepo.manager.findOne(InvestOrder, {
      where: {
        stageId,
        investorId,
      },
      order: {
        firstTranscationDate: 'ASC',
      },
    });

    if (!order) {
      throw new AppException('Investment order not found', { httpCode: 400 });
    }

    // Get all investment funding records
    const investments = await this.investFundingRepo.find({
      where: {
        stageId,
        investorId,
        type: InvestFundingPlusType.INVESTMENT,
      },
    });

    if (investments.length === 0) {
      return;
    }

    // Find earliest startDate and latest endDate
    const startDate = investments.reduce((earliest, curr) => (curr.startDate < earliest ? curr.startDate : earliest), investments[0].startDate);
    const endDate = investments.reduce((latest, curr) => (curr.endDate > latest ? curr.endDate : latest), investments[0].endDate);

    // Calculate monthly periods
    const periods = this.calculateMonthlyPeriods(startDate, endDate, stage.loanStartDate, stage.loanRepaymentDate);

    // Process each monthly period
    for (const period of periods) {
      if (newRepaymentDate && newRepaymentDate.getTime() > period.endDate.getTime()) {
        continue;
      }
      // Calculate total interest for the period
      const interestSum = await this.investInterestRepo
        .createQueryBuilder('interest')
        .select('SUM(interest.money)', 'total')
        .addSelect('SUM(interest.net)', 'net')
        .addSelect('SUM(interest.tax)', 'tax')
        .addSelect('SUM(interest.fee)', 'fee')
        .where('interest.stageId = :stageId', { stageId })
        .andWhere('interest.investorId = :investorId', { investorId })
        .andWhere('interest.date BETWEEN :start AND :end', {
          start: period.startDate,
          end: period.endDate,
        })
        .getRawOne();

      const totalInterest = new Decimal(interestSum?.total || 0);
      const totalNet = new Decimal(interestSum?.net || 0);
      const totalTax = new Decimal(interestSum?.tax || 0);
      const totalFee = new Decimal(interestSum?.fee || 0);

      if (totalInterest.equals(0)) continue;

      const r = await this.createFundingRecord({
        stageId,
        orderId: order.id,
        investorId,
        type: InvestFundingPlusType.INTEREST_DISTRIBUTION,
        value: totalNet,
        startDate: new Date(period.startDate),
        endDate: new Date(period.endDate),
        isDoubleEntry: true
      });

      await this.createFundingRecord({
        stageId,
        orderId: order.id,
        investorId,
        type: InvestFundingMinusType.INTEREST_EARN,
        value: totalNet,
        startDate: period.startDate,
        endDate: period.endDate,
      });

      if (totalTax.gt(0)) {
        await this.createFundingRecord({
          stageId,
          orderId: order.id,
          investorId,
          type: InvestFundingPlusType.TAX_WITHOLDING,
          value: totalTax,
          startDate: new Date(period.startDate),
          endDate: new Date(period.endDate),
        });
        await this.createFundingRecord({
          stageId,
          orderId: order.id,
          investorId,
          type: InvestFundingMinusType.TAX_PAYMENT,
          value: totalTax,
          startDate: new Date(period.startDate),
          endDate: new Date(period.endDate),
          isDoubleEntry: true
        });
      }

      if (totalFee.gt(0)) {
        await this.createFundingRecord({
          stageId,
          orderId: order.id,
          investorId,
          type: InvestFundingPlusType.MANAGEMENT_CHARGE,
          value: totalFee,
          startDate: new Date(period.startDate),
          endDate: new Date(period.endDate),
        });
        await this.createFundingRecord({
          stageId,
          orderId: order.id,
          investorId,
          type: InvestFundingMinusType.MANAGEMENT_PAYMENT,
          value: totalFee,
          startDate: new Date(period.startDate),
          endDate: new Date(period.endDate),
          isDoubleEntry: true
        });
      }
    }
  }

  public calculateMonthlyPeriods(investStartDate: Date, investEndDate: Date, loanStartDate: Date, loanRepaymentDate: Date) {
    const periods = [];
    // Get the day of month from loanStartDate (e.g. 26)
    const loanStartDay = loanStartDate.getDate();
    // Calculate cutoff day (e.g. 25)
    const loanEndDay = loanStartDay - 1;

    let currentStart = new Date(investStartDate);
    let endDate = new Date(Math.min(investEndDate.getTime(), loanRepaymentDate.getTime()))
    while (currentStart < endDate) {
      // Calculate period end date
      let periodEnd = new Date(currentStart);
      periodEnd.setDate(loanEndDay);
      
      const daysInMonth = new Date(currentStart.getFullYear(), currentStart.getMonth() + 2, 0).getDate();
      if (daysInMonth < loanEndDay) {
        periodEnd.setMonth(periodEnd.getMonth() + 1);
        periodEnd.setDate(0);
      } else {
        const currentStartDay = currentStart.getDate();
        if (loanEndDay == 0) {
          periodEnd.setMonth(periodEnd.getMonth() + 1);
          periodEnd.setDate(loanEndDay);
        } else if (currentStartDay > loanEndDay) {
          periodEnd.setDate(loanEndDay);
          periodEnd.setMonth(periodEnd.getMonth() + 1);
        } else {
          periodEnd.setDate(loanEndDay);
        }
      }

      if(loanEndDay == 0){
        periodEnd = new Date(currentStart.getFullYear(), currentStart.getMonth() + 1, 0);
      } else if (periodEnd < currentStart) {
        // If periodEnd is before currentStart, move to next month
        periodEnd = new Date(currentStart);
        periodEnd.setMonth(periodEnd.getMonth() + 1);
        periodEnd.setDate(loanEndDay);
      } 
      
      // If periodEnd exceeds endDate, use endDate
      if (periodEnd > endDate) {
        periodEnd.setTime(endDate.getTime());
      }


      periods.push({
        startDate: new Date(currentStart),
        endDate: periodEnd,
      });

      // Move to next period start (cutoff day + 1)
      currentStart = new Date(periodEnd);
      currentStart.setDate(currentStart.getDate() + 1);
    }

    return periods;
  }

  async updateFinalWithdraw(stageId: number, investorId: number): Promise<void> {
    let order = await this.investOrderRepo.findOne({
      where: {
        stageId,
        investorId
      }
    });
    let stage = await this.stageRepo.findOneBy({id: stageId})

    // Query for INVESTMENT and WITHDRAWAL fundings
    const fundings = await this.investFundingRepo.find({
      where: {
        stageId: stageId,
        investorId: investorId,
        type: In([ InvestFundingPlusType.INVESTMENT, InvestFundingMinusType.WITHDRAWAL]),
      },
    });

    // Calculate sums
    const investmentSum = fundings
      .filter(funding => funding.type === InvestFundingPlusType.INVESTMENT)
      .reduce((sum, funding) => sum.add(funding.value), new Decimal(0));

    const withdrawalSum = fundings
      .filter(funding => funding.type ===  InvestFundingMinusType.WITHDRAWAL)
      .reduce((sum, funding) => sum.add(funding.value), new Decimal(0));

    // Check if INVESTMENT sum is greater than WITHDRAWAL sum
    if (investmentSum.gt(withdrawalSum)) {
      const finallyWithdrawFunding = this.investFundingRepo.create({
        investOrderId: order.id,
        stageId: stageId,
        investorId: investorId,
        type: InvestFundingMinusType.WITHDRAWAL,
        value: investmentSum.sub(withdrawalSum),
        startDate: stage.loanRepaymentDate, // 还款时间为最后一个提现时间
        endDate: stage.loanRepaymentDate, // 还款时间为最后一个提现时间
      });

      // Save the new funding
      await this.investFundingRepo.save(finallyWithdrawFunding);
    }
  }

  private async createFundingRecord(params: {
    stageId: number;
    orderId: number;
    investorId: number;
    type: InvestFundingPlusType | InvestFundingMinusType;
    value: Decimal;
    startDate: Date;
    endDate: Date;
    transcationDate?: Date;
    isDoubleEntry?: boolean;
    doubleEntryId?: number;
  }) {
    return await this.investFundingRepo.save({
      stageId: params.stageId,
      investorId: params.investorId,
      investOrderId: params.orderId,
      type: params.type,
      value: params.value,
      startDate: params.startDate,
      endDate: params.endDate,
      transcationDate: params.transcationDate,
      isDoubleEntry: params.isDoubleEntry,
      doubleEntryId: params.doubleEntryId,
    });
  }


  async updateInvestEnd(stageId: number, preInvestEnd: Date, NowinvestEnd: Date){
    const fundings = await this.investFundingRepo.find({
      where: { stageId, type: In([InvestFundingPlusType.INVESTMENT, InvestFundingMinusType.WITHDRAWAL]), endDate: preInvestEnd }
    });

    for (const funding of fundings) {
      funding.endDate = NowinvestEnd;
    }

    await this.investFundingRepo.save(fundings);
  }
}
